#ifndef _RENDERER_H
#define _RENDERER_H

#include <string>
#include <vector>
#include <iostream>
#include <tr1/unordered_map>
#include <tr1/memory>
#include <cassert>

#include <sigc++/sigc++.h>
#include <GL/gl.h>

#include "scenenode.h"
#include "keyed_container.h"
#include "filemod.h"
#include "joeserialize.h"
#include "renderpassinfo.h"

struct RenderTexture
{
	RenderTexture() : handle(0),autoMipmap(false),width(512),height(512),origw(512),origh(512),origMultiples(false) {}
	
	GLuint handle;
	GLenum target;
	GLenum internalFormat;
	
	// these are only set for render targets
	bool autoMipmap;
	unsigned int width, height;
	float origw, origh;
	bool origMultiples;
	
	std::string path;
	std::string nodeName;
	SceneNode::fields_type fieldCache;
	FileModificationTime modTime;
};

struct RenderShader
{
	GLuint handle;
	
	std::string path;
	std::set <std::string> defines; //used for exporting only
	
	FileModificationTime modTime;
};

struct RenderModel
{
	RenderModel() : vao(0), elementCount(0), elementVbo(0) {}
	
	GLuint vao;
	std::vector <GLuint> vbos;
	GLuint elementVbo;
	int elementCount;
	
	std::string path;
	SceneNode::fields_type fieldCache;
	FileModificationTime modTime;
};

template <typename texture_apply_functor, typename model_apply_functor>
struct ExternalModelContext
{
	struct ExternalUniformData
	{
		std::string name; // the name allows us to look up the uniform location
		std::tr1::shared_ptr <std::vector <float> > data;
	};
	
	struct ExternalTextureData
	{
		std::string name; // the name allows us to look up the TU
		texture_apply_functor textureApply; // a functor that, when executed, does something like glBindTexture. it must return a GLint that defines the texture target.
	};
	
	model_apply_functor modelApply; // a functor that, when executed, does something like glDrawElements
	std::vector <ExternalUniformData> uniformOverrides;
	std::vector <ExternalTextureData> textureBindingOverrides;
};

struct RenderPass
{
	struct UniformData
	{
		GLint location;
		std::tr1::shared_ptr <std::vector <float> > data;
	};
	
	struct TextureData
	{
		GLint tu;
		keyed_container_handle texture;
	};
	
	struct ModelContext
	{
		// The handle allows us to look up the GLuint of the model's vao.
		keyed_container_handle model;
		
		// This contains per-model overrides for texture data but could just as well be empty
		// The handle allows us to look up the GLuint of the texture.
		std::vector <TextureData> textureBindingOverrides;
		
		// This contains per-model overrides for uniform data but is often empty
		std::vector <UniformData> uniformOverrides;
	};
	
	// these fields are used to remember mappings for use by external model contexts
	// this is used to remember how variable names correspond to uniform locations
	std::tr1::unordered_map <std::string, GLint> variableNameToUniformLocation;
	// this is used to remember how texture names correspond to texture unit numbers
	std::tr1::unordered_map <std::string, GLint> textureNameToTextureUnit;
	
	GLbitfield clearMask;
	GLfloat clearColor[4];
	GLfloat clearDepth;
	GLint clearStencil;
	std::vector <ModelContext> models;
	
	GLuint shaderProgram;
	keyed_container<RenderShader>::handle vertexShader;
	keyed_container<RenderShader>::handle fragmentShader;
	SceneNode::fields_type shaderProgramFieldCache;
	
	std::vector <std::string> shaderAttributeBindings;
	
	// variables that can be overridden (or not) by specific models
	std::vector <UniformData> defaultUniforms;
	
	// texture bindings that can be overridden (or not) by specific models
	std::vector <TextureData> defaultTextureBindings;
	
	// render states
	struct RenderState
	{
		GLenum pname;
		enum RenderStateType
		{
			SS_ENUM,
			SS_FLOAT,
			SS_FLOAT2,
			SS_FLOAT4, //(color)
			SS_INT
		} type;
		GLint param[4];
		GLfloat fparam[4];
		RenderState(const std::string & name, RenderStateType newtype, const std::string & value);
	};
	std::vector <GLenum> stateEnable;
	std::vector <GLenum> stateDisable;
	std::vector <std::pair<GLenum,unsigned int> > stateEnablei;
	std::vector <std::pair<GLenum,unsigned int> > stateDisablei;
	std::vector <RenderState> stateEnum;
	
	// render target information
	GLuint framebufferObject, renderbuffer;
	unsigned int width, height;
	std::vector <keyed_container<RenderTexture>::handle> renderTargets;
	std::vector <GLenum> renderTargetAttachmentPoints; // this vector's size is the same as above
	std::vector <std::string> renderTargetNames; // this vector's size is the same as above
	std::vector <std::string> renderTargetVariables; // this vector's size is the same as above. shader variables for the render target outputs
	
	// samplers (using OpenGL 3.3 samplers here would be ideal but there's not wide support yet)
	struct Sampler
	{
		std::string name;
		std::vector <RenderState> state;
	};
	std::vector <Sampler> samplers;
	
	bool enable;
	
	RenderPass() : framebufferObject(0), renderbuffer(0), enable(true) {}
	RealtimeExportPassInfo exportRealtime(const SceneNode & passNode, const keyed_container <RenderShader> & shaders, const keyed_container <RenderTexture> & textures, std::ostream & log) const;
};

class Renderer
{
	public:
		typedef std::tr1::unordered_map <const SceneNode*, keyed_container_handle> SceneMapping;
		
		Renderer();
		bool initialize();
		
		/// if fast is true, then only check for updated assets and reload those, but otherwise do nothing
		void updateConfiguration(const SceneNode & root, unsigned int newWidth, unsigned int newHeight, bool fast = false);
		
		void draw();
		
		template <typename external_texture_apply_functor, typename external_model_apply_functor>
		void drawWithExternals(const std::vector <ExternalModelContext <external_texture_apply_functor, external_model_apply_functor> > & externalModels);
		
		void handleDelete(RenderPass & pass, keyed_container_handle handle, RenderPass &) {assert(0);}
		void handleDelete(RenderPass & pass, keyed_container_handle handle, RenderShader & shader);
		void handleShaderUpdate(RenderPass & pass, keyed_container_handle handle, RenderShader & shader);
		void handleDelete(RenderPass & pass, keyed_container_handle handle, RenderModel & model) {} // we rebuild models in the pass every time anything is changed, so we don't need to do anything special here
		void handleDelete(RenderPass & pass, keyed_container_handle handle, RenderTexture & texture) {} // we rebuild texture bindings in the pass every time anything is changed, so we don't need to do anything special here
		
		sigc::signal <void, std::string> logOutput;
		sigc::signal <void> logClear;
		
		const std::string & getSupportedGlVersion() const {return supportedGlVersion;}
		const std::string & getRequiredGlVersion() const;
		
		bool exportRealtime(const SceneNode & root, const std::string & file, std::ostream & log);
		
	private:
		reseatable_reference <const SceneNode> sceneRoot;
		bool doFastUpdate;
		bool modelsOrTexturesTouched;
		
		unsigned int width, height;
		bool logToWindow;
		std::string supportedGlVersion;
		
		SceneMapping passMap;
		keyed_container <RenderPass> passes;
		std::vector <RenderPass*> passOrder;
		void deletePass(RenderPass & arg);
		void updatePass(std::pair<RenderPass &, const SceneNode *> args);
		void addPass(std::pair<RenderPass &, const SceneNode *> args);
		
		void passLinkShaderProgram(RenderPass & pass, const SceneNode * node, bool force);
		void passLinkShaderProgramInternal(RenderPass & pass, const std::string & programName);
		void passAssignModels(RenderPass & pass, const SceneNode * node);
		void passSetState(RenderPass & pass, const SceneNode * node);
		void passSetFBO(RenderPass & pass, const SceneNode * node);
		void passSetConfiguration(RenderPass & pass, const SceneNode * node);
		void assignUniformData(RenderPass::UniformData & uniform, SceneNode * variableNode);
		
		void rebindModelVaos();
		
		SceneMapping modelMap;
		keyed_container <RenderModel> models;
		void deleteModel(RenderModel & arg);
		void updateModel(std::pair<RenderModel &, const SceneNode *> args);
		void addModel(std::pair<RenderModel &, const SceneNode *> args);
		
		SceneMapping shaderMap;
		keyed_container <RenderShader> shaders;
		void deleteShader(RenderShader & arg);
		void updateShader(std::pair<RenderShader &, const SceneNode *> args);
		void addShader(std::pair<RenderShader &, const SceneNode *> args);
		
		SceneMapping textureMap;
		keyed_container <RenderTexture> textures;
		void deleteTexture(RenderTexture & arg);
		void updateTexture(std::pair<RenderTexture &, const SceneNode *> args);
		void addTexture(std::pair<RenderTexture &, const SceneNode *> args);
		
		void logError(const std::string & out)
		{
			if (logToWindow)
				logOutput(std::string("ERROR: ")+out);
			else
				std::cerr << "ERROR: " << out << std::endl;
		}
		
		bool checkForOpenGLErrors(std::string activity_description);
		
		void applyUniform(GLint location, const std::tr1::shared_ptr <std::vector <float> > data);
		void applyTextureBind(const RenderPass::TextureData & texture, const std::vector <RenderPass::Sampler> & samplers, bool verbose);
		void applyRenderState(RenderPass::RenderState val);
};

#endif
